// base64.hpp: Base64 filter

// Copyright Takeshi Mouri 2006, 2007.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// See http://hamigaki.sourceforge.jp/libs/iostreams for library home page.

#ifndef HAMIGAKI_IOSTREAMS_FILTER_BASE64_HPP
#define HAMIGAKI_IOSTREAMS_FILTER_BASE64_HPP

//#include <hamigaki/iostreams/arbitrary_positional_facade.hpp>
#include "arbitrary_positional_facade.hpp"
#include <boost/iostreams/detail/ios.hpp>
#include <boost/iostreams/read.hpp>
#include <boost/iostreams/write.hpp>
#include <boost/integer.hpp>
#include <algorithm>

namespace hamigaki { namespace iostreams {

	struct base64_traits
	{
		static const char padding = '=';

		static char encode(unsigned char n)
		{
			const char table[] =
				"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
				"abcdefghijklmnopqrstuvwxyz"
				"0123456789+/";

			return table[n];
		}

		static unsigned char decode(char c)
		{
			// TODO: should support non-ASCII
			const unsigned char table[] =
			{
				0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
				0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

				0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
				0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

				0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
				0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, 0xFF, 0x3F,

				0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
				0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

				0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
				0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,

				0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
				0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

				0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
				0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,

				0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
				0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
			};
			unsigned char uc = static_cast<unsigned char>(c);
			if (static_cast<std::size_t>(uc) >= sizeof(table))
				throw BOOST_IOSTREAMS_FAILURE("bad Base64 sequence");
			unsigned char val = table[uc];
			if (val == 0xFF)
				throw BOOST_IOSTREAMS_FAILURE("bad Base64 sequence");
			return val;
		}
	};

	struct urlsafe_base64_traits
	{
		static const char padding = '=';

		static char encode(unsigned char n)
		{
			const char table[] =
				"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
				"abcdefghijklmnopqrstuvwxyz"
				"0123456789-_";

			return table[n];
		}

		static unsigned char decode(char c)
		{
			// TODO: should support non-ASCII
			const unsigned char table[] =
			{
				0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
				0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

				0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
				0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

				0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
				0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF,

				0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
				0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

				0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
				0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,

				0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
				0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F,

				0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
				0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,

				0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
				0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
			};
			unsigned char uc = static_cast<unsigned char>(c);
			if (static_cast<std::size_t>(uc) >= sizeof(table))
				throw BOOST_IOSTREAMS_FAILURE("bad URL-safe Base64 sequence");
			unsigned char val = table[uc];
			if (val == 0xFF)
				throw BOOST_IOSTREAMS_FAILURE("bad URL-safe Base64 sequence");
			return val;
		}
	};

	template<class Traits>
	class basic_base64_encoder
		: public arbitrary_positional_facade<basic_base64_encoder<Traits>, char, 3>
	{
		friend class core_access;

		typedef boost::uint_t<24>::fast uint24_t;

	public:
		typedef char char_type;

		struct category
			: public boost::iostreams::output
			, public boost::iostreams::filter_tag
			, public boost::iostreams::multichar_tag
			, public boost::iostreams::closable_tag
		{};

	private:
		static uint24_t char_to_uint24(char c)
		{
			return static_cast<uint24_t>(static_cast<unsigned char>(c) & 0xFF);
		}

		static void encode(char* dst, const char* src)
		{
			uint24_t tmp =
				(char_to_uint24(src[0]) << 16) |
				(char_to_uint24(src[1]) <<  8) |
				(char_to_uint24(src[2])      ) ;

			dst[0] = Traits::encode((tmp >> 18) & 0x3F);
			dst[1] = Traits::encode((tmp >> 12) & 0x3F);
			dst[2] = Traits::encode((tmp >>  6) & 0x3F);
			dst[3] = Traits::encode((tmp      ) & 0x3F);
		}

		template<class Sink>
		std::streamsize write_blocks(Sink& sink, const char* s, std::streamsize n)
		{
			for (int i = 0; i < n; ++i)
			{
				char buf[4];
				encode(buf, s);
				boost::iostreams::write(sink, buf, sizeof(buf));
				s += 3;
			}
			return n*3;
		}

		template<class Sink>
		void close_with_flush(Sink& sink, const char* s, std::streamsize n)
		{
			if (n != 0)
			{
				char src[3] = { 0, 0, 0 };
				std::copy(s, s+n, &src[0]);

				char buf[4];
				encode(buf, src);

				if (n == 1)
					buf[2] = Traits::padding;
				buf[3] = Traits::padding;

				boost::iostreams::write(sink, buf, sizeof(buf));
			}
		}
	};

	template<class Traits>
	class basic_base64_decoder
		: public arbitrary_positional_facade<basic_base64_decoder<Traits>, char, 3>
	{
		friend class core_access;

		typedef boost::uint_t<24>::fast uint24_t;
		typedef boost::uint_t<6>::least uint6_t;

	public:
		typedef char char_type;

		struct category
			: public boost::iostreams::input
			, public boost::iostreams::filter_tag
			, public boost::iostreams::multichar_tag
			, public boost::iostreams::closable_tag
		{};

	private:
		static char uint24_to_char(uint24_t n)
		{
			return static_cast<char>(static_cast<unsigned char>(n & 0xFF));
		}

		static uint24_t decode(char c)
		{
			return Traits::decode(c);
		}

		static std::streamsize decode(char* dst, const char* src)
		{
			char buf[4];
			std::copy(src, src+4, buf);

			if (src[3] == Traits::padding)
			{
				buf[3] = Traits::encode(0);
				if (src[2] == Traits::padding)
					buf[2] = Traits::encode(0);
			}

			uint24_t tmp =
				(decode(buf[0]) << 18) |
				(decode(buf[1]) << 12) |
				(decode(buf[2]) <<  6) |
				(decode(buf[3])      ) ;

			std::streamsize n = 0;
			dst[n++] = uint24_to_char(tmp >> 16);
			if (src[2] != Traits::padding)
				dst[n++] = uint24_to_char(tmp >>  8);
			if (src[3] != Traits::padding)
				dst[n++] = uint24_to_char(tmp      );
			return n;
		}

		template<class Source>
		std::streamsize read_blocks(Source& src, char* s, std::streamsize n)
		{
			std::streamsize total = 0;
			for (int i = 0; i < n; ++i)
			{
				char buf[4];

				std::streamsize res = boost::iostreams::read(src, buf, sizeof(buf));
				if (res == -1)
				{
					break;
				}

				std::streamsize amt = decode(s, buf);
				s += amt;
				total += amt;
			}
			return (total != 0) ? total : -1;
		}

		template<class Source>
		void close_with_flush(Source&, const char*, std::streamsize)
		{
		}
	};

	typedef basic_base64_encoder<base64_traits> base64_encoder;
	typedef basic_base64_decoder<base64_traits> base64_decoder;

	typedef basic_base64_encoder<urlsafe_base64_traits> urlsafe_base64_encoder;
	typedef basic_base64_decoder<urlsafe_base64_traits> urlsafe_base64_decoder;

} } // End namespaces iostreams, hamigaki.

#endif // HAMIGAKI_IOSTREAMS_FILTER_BASE64_HPP
